package com.booking.common.utils;

/**
 * @author Qikai.Lin
 * @date 2023/4/21
 */

import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cglib.beans.BeanCopier;

import javax.validation.constraints.NotNull;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
import java.util.stream.Collectors;

@Slf4j
public class ConvertUtils {

    static final Map<Classes, BeanCopier> copyCache = new ConcurrentHashMap<>();

    /**
     * convert bean with other consumer
     *
     * @param target     target class
     * @param sourceBean
     * @param consumer
     * @param <S>        sourceBean
     * @param <T>        targetBean
     * @return
     */
    public static <S, T> T convert(Class<T> target, S sourceBean, BiConsumer<S, T> consumer) {
        if (sourceBean == null) {
            return null;
        }
        Class<?> source = sourceBean.getClass();
        T targetBean = convert(source, target, sourceBean);
        consumer.accept(sourceBean, targetBean);
        return targetBean;
    }

    /**
     * convert bean
     *
     * @param target
     * @param sourceBean
     * @param <S>        sourceBean
     * @param <T>        targetBean
     * @return
     */
    public static <S, T> T convert(Class<T> target, S sourceBean) {
        return convert(sourceBean.getClass(), target, sourceBean);
    }

    /**
     * 将source里字段copy到target
     *
     * @param targetBean
     * @param sourceBean
     * @param <S>
     * @param <T>
     */
    public static <S, T> void copy(@NotNull T targetBean, @NotNull S sourceBean) {
        Class<?> source = sourceBean.getClass();
        Class<?> target = targetBean.getClass();
        Classes classes = Classes.of(source, target);
        BeanCopier beanCopier = copyCache.get(classes);
        if (beanCopier == null) {
            beanCopier = BeanCopier.create(source, target, false);
            copyCache.put(classes, beanCopier);
        }

        beanCopier.copy(sourceBean, targetBean, null);
    }

    /**
     * convert list
     *
     * @param target
     * @param sourceList
     * @param <S>        sourceBean
     * @param <T>        targetBean
     * @return
     */
    public static <S, T> List<T> convertList(Class<T> target, List<S> sourceList) {
        return Optional.ofNullable(sourceList).orElse(new ArrayList<>(1)).stream().map(sourceBean -> {
            return convert(sourceBean.getClass(), target, sourceBean);
        }).collect(Collectors.toList());
    }

    /**
     * 转换list
     *
     * @param target
     * @param sourceList
     * @param consumer
     * @param <S>        sourceBean
     * @param <T>        targetBean
     * @return
     */
    public static <S, T> List<T> convertList(Class<T> target, List<S> sourceList, BiConsumer<S, T> consumer) {
        return Optional.ofNullable(sourceList).orElse(new ArrayList<>(1)).stream().map(sourceBean -> {
            return convert(target, sourceBean, consumer);
        }).collect(Collectors.toList());
    }

    /**
     * 转换list时遇到异常则跳过该对象
     *
     * @param target
     * @param sourceList
     * @param consumer
     * @param <S>
     * @param <T>
     * @return
     */
    public static <S, T> List<T> convertListSkipException(Class<T> target,
                                                          List<S> sourceList,
                                                          BiConsumer<S, T> consumer) {
        return Optional.ofNullable(sourceList).orElse(new ArrayList<>(1)).stream().map(sourceBean -> {
            try {
                return convert(target, sourceBean, consumer);
            } catch (Throwable e) {
                log.warn("convert error!", e);
                return null;
            }
        }).filter(Objects::nonNull).collect(Collectors.toList());
    }

    @SneakyThrows
    private static <S, T> T convert(Class<?> source, Class<T> target, S sourceBean) {
        Classes classes = Classes.of(source, target);
        BeanCopier beanCopier = copyCache.get(classes);
        if (beanCopier == null) {
            beanCopier = BeanCopier.create(source, target, false);
            copyCache.put(classes, beanCopier);
        }

        T targetBean = target.newInstance();
        beanCopier.copy(sourceBean, targetBean, null);
        return targetBean;
    }

    static class Classes {
        Class source;
        Class target;

        private Classes() {
        }

        public static Classes of(Class source, Class target) {
            Classes classes = new Classes();
            classes.source = source;
            classes.target = target;
            return classes;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o)
                return true;
            if (o == null || getClass() != o.getClass())
                return false;
            Classes classes = (Classes) o;
            return Objects.equals(source, classes.source) && Objects.equals(target, classes.target);
        }

        @Override
        public int hashCode() {
            return Objects.hash(source, target);
        }
    }
}



